/*
* Fri Feb 28 07:47:05 1997 Doug Lea (dl at gee)
* Based on PolyLineFigure
*/
package CH.ifa.draw.contrib;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Vector;
import CH.ifa.draw.figures.AttributeFigure;
import CH.ifa.draw.framework.Connector;
import CH.ifa.draw.framework.Figure;
import CH.ifa.draw.framework.Handle;
import CH.ifa.draw.framework.Locator;
import CH.ifa.draw.standard.AbstractLocator;
import CH.ifa.draw.util.Geom;
import CH.ifa.draw.util.StorableInput;
import CH.ifa.draw.util.StorableOutput;
/**
* A scalable, rotatable polygon with an arbitrary number of points
*/
public class PolygonFigure extends AttributeFigure {
/**
* Distance threshold for smoothing away or locating points
**/
static final int TOO_CLOSE = 2;
/*
* Serialization support.
*/
private static final long serialVersionUID = 6254089689239215026L;
private int polygonFigureSerializedDataVersion = 1;
protected Polygon fPoly = new Polygon();
public PolygonFigure() {
super();
}
public PolygonFigure(int x, int y) {
fPoly.addPoint(x, y);
}
public PolygonFigure(Polygon p) {
fPoly = new Polygon(p.xpoints, p.ypoints, p.npoints);
}
public Rectangle displayBox() {
return bounds(fPoly);
}
public boolean isEmpty() {
return (fPoly.npoints < 3 ||
(size().width < TOO_CLOSE) && (size().height < TOO_CLOSE));
}
public Vector<Handle> handles() {
Vector handles = new Vector(fPoly.npoints);
for (int i = 0; i < fPoly.npoints; i++)
handles.addElement(new PolygonHandle(this, locator(i), i));
handles.addElement(new PolygonScaleHandle(this));
//handles.addElement(new PolygonPointAddHandle(this));
return handles;
}
public void basicDisplayBox(Point origin, Point corner) {
Rectangle r = displayBox();
int dx = origin.x - r.x;
int dy = origin.y - r.y;
fPoly.translate(dx, dy);
r = displayBox();
Point oldCorner = new Point(r.x + r.width, r.y + r.height);
Polygon p = getPolygon();
scaleRotate(oldCorner, p, corner);
}
/**
* return a copy of the raw polygon
**/
public Polygon getPolygon() {
return new Polygon(fPoly.xpoints, fPoly.ypoints, fPoly.npoints);
}
public Point center() {
return center(fPoly);
}
public Enumeration points() {
Vector pts = new Vector(fPoly.npoints);
for (int i = 0; i < fPoly.npoints; ++i)
pts.addElement(new Point(fPoly.xpoints[i], fPoly.ypoints[i]));
return pts.elements();
}
public int pointCount() {
return fPoly.npoints;
}
public void basicMoveBy(int dx, int dy) {
fPoly.translate(dx, dy);
}
public void drawBackground(Graphics g) {
g.fillPolygon(fPoly);
}
public void drawFrame(Graphics g) {
g.drawPolygon(fPoly);
}
public boolean containsPoint(int x, int y) {
return fPoly.contains(x, y);
}
public Connector connectorAt(int x, int y) {
return new ChopPolygonConnector(this);
}
/**
* Adds a node to the list of points.
*/
public void addPoint(int x, int y) {
fPoly.addPoint(x, y);
changed();
}
/**
* Changes the position of a node.
*/
public void setPointAt(Point p, int i) {
willChange();
fPoly.xpoints[i] = p.x;
fPoly.ypoints[i] = p.y;
changed();
}
/**
* Insert a node at the given point.
*/
public void insertPointAt(Point p, int i) {
willChange();
int n = fPoly.npoints + 1;
int[] xs = new int[n];
int[] ys = new int[n];
for (int j = 0; j < i; ++j) {
xs[j] = fPoly.xpoints[j];
ys[j] = fPoly.ypoints[j];
}
xs[i] = p.x;
ys[i] = p.y;
for (int j = i; j < fPoly.npoints; ++j) {
xs[j+1] = fPoly.xpoints[j];
ys[j+1] = fPoly.ypoints[j];
}
fPoly = new Polygon(xs, ys, n);
changed();
}
public void removePointAt(int i) {
willChange();
int n = fPoly.npoints - 1;
int[] xs = new int[n];
int[] ys = new int[n];
for (int j = 0; j < i; ++j) {
xs[j] = fPoly.xpoints[j];
ys[j] = fPoly.ypoints[j];
}
for (int j = i; j < n; ++j) {
xs[j] = fPoly.xpoints[j+1];
ys[j] = fPoly.ypoints[j+1];
}
fPoly = new Polygon(xs, ys, n);
changed();
}
/**
* Scale and rotate relative to anchor
**/
public void scaleRotate(Point anchor, Polygon originalPolygon, Point p) {
willChange();
// use center to determine relative angles and lengths
Point ctr = center(originalPolygon);
double anchorLen = Geom.length(ctr.x, ctr.y, anchor.x, anchor.y);
if (anchorLen > 0.0) {
double newLen = Geom.length(ctr.x, ctr.y, p.x, p.y);
double ratio = newLen / anchorLen;
double anchorAngle = Math.atan2(anchor.y - ctr.y, anchor.x - ctr.x);
double newAngle = Math.atan2(p.y - ctr.y, p.x - ctr.x);
double rotation = newAngle - anchorAngle;
int n = originalPolygon.npoints;
int[] xs = new int[n];
int[] ys = new int[n];
for (int i = 0; i < n; ++i) {
int x = originalPolygon.xpoints[i];
int y = originalPolygon.ypoints[i];
double l = Geom.length(ctr.x, ctr.y, x, y) * ratio;
double a = Math.atan2(y - ctr.y, x - ctr.x) + rotation;
xs[i] = (int)(ctr.x + l * Math.cos(a) + 0.5);
ys[i] = (int)(ctr.y + l * Math.sin(a) + 0.5);
}
fPoly = new Polygon(xs, ys, n);
}
changed();
}
/**
* Remove points that are nearly colinear with others
**/
public void smoothPoints() {
willChange();
boolean removed = false;
int n = fPoly.npoints;
do {
removed = false;
int i = 0;
while (i < n && n >= 3) {
int nxt = (i + 1) % n;
int prv = (i - 1 + n) % n;
if ((distanceFromLine(fPoly.xpoints[prv], fPoly.ypoints[prv],
fPoly.xpoints[nxt], fPoly.ypoints[nxt],
fPoly.xpoints[i], fPoly.ypoints[i]) < TOO_CLOSE)) {
removed = true;
--n;
for (int j = i; j < n; ++j) {
fPoly.xpoints[j] = fPoly.xpoints[j+1];
fPoly.ypoints[j] = fPoly.ypoints[j+1];
}
}
else
++i;
}
} while(removed);
if (n != fPoly.npoints)
fPoly = new Polygon(fPoly.xpoints, fPoly.ypoints, n);
changed();
}
/**
* Splits the segment at the given point if a segment was hit.
* @return the index of the segment or -1 if no segment was hit.
*/
public int splitSegment(int x, int y) {
int i = findSegment(x, y);
if (i != -1) {
insertPointAt(new Point(x, y), i+1);
return i + 1;
}
else
return -1;
}
public Point pointAt(int i) {
return new Point(fPoly.xpoints[i], fPoly.ypoints[i]);
}
/**
* Return the point on the polygon that is furthest from the center
**/
public Point outermostPoint() {
Point ctr = center();
int outer = 0;
long dist = 0;
for (int i = 0; i < fPoly.npoints; ++i) {
long d = Geom.length2(ctr.x, ctr.y, fPoly.xpoints[i], fPoly.ypoints[i]);
if (d > dist) {
dist = d;
outer = i;
}
}
return new Point(fPoly.xpoints[outer], fPoly.ypoints[outer]);
}
/**
* Gets the segment that is hit by the given point.
* @return the index of the segment or -1 if no segment was hit.
*/
public int findSegment(int x, int y) {
double dist = TOO_CLOSE;
int best = -1;
for (int i = 0; i < fPoly.npoints; i++) {
int n = (i + 1) % fPoly.npoints;
double d = distanceFromLine(fPoly.xpoints[i], fPoly.ypoints[i],
fPoly.xpoints[n], fPoly.ypoints[n],
x, y);
if (d < dist) {
dist = d;
best = i;
}
}
return best;
}
public Point chop(Point p) {
return chop(fPoly, p);
}
public void write(StorableOutput dw) {
super.write(dw);
dw.writeInt(fPoly.npoints);
for (int i = 0; i < fPoly.npoints; ++i) {
dw.writeInt(fPoly.xpoints[i]);
dw.writeInt(fPoly.ypoints[i]);
}
}
public void read(StorableInput dr) throws IOException {
super.read(dr);
int size = dr.readInt();
int[] xs = new int[size];
int[] ys = new int[size];
for (int i = 0; i < size; i++) {
xs[i] = dr.readInt();
ys[i] = dr.readInt();
}
fPoly = new Polygon(xs, ys, size);
}
/**
* Creates a locator for the point with the given index.
*/
public static Locator locator(final int pointIndex) {
return new AbstractLocator() {
public Point locate(Figure owner) {
PolygonFigure plf = (PolygonFigure)owner;
// guard against changing PolygonFigures -> temporary hack
if (pointIndex < plf.pointCount())
return ((PolygonFigure)owner).pointAt(pointIndex);
return new Point(-1, -1);
}
};
}
/**
* compute distance of point from line segment, or
* Double.MAX_VALUE if perpendicular projection is outside segment; or
* If pts on line are same, return distance from point
**/
public static double distanceFromLine(int xa, int ya,
int xb, int yb,
int xc, int yc) {
// source:http://vision.dai.ed.ac.uk/andrewfg/c-g-a-faq.html#q7
//Let the point be C (XC,YC) and the line be AB (XA,YA) to (XB,YB).
//The length of the
// line segment AB is L:
//
// ___________________
// | 2 2
// L = \| (XB-XA) + (YB-YA)
//and
//
// (YA-YC)(YA-YB)-(XA-XC)(XB-XA)
// r = -----------------------------
// L**2
//
// (YA-YC)(XB-XA)-(XA-XC)(YB-YA)
// s = -----------------------------
// L**2
//
// Let I be the point of perpendicular projection of C onto AB, the
//
// XI=XA+r(XB-XA)
// YI=YA+r(YB-YA)
//
// Distance from A to I = r*L
// Distance from C to I = s*L
//
// If r < 0 I is on backward extension of AB
// If r>1 I is on ahead extension of AB
// If 0<=r<=1 I is on AB
//
// If s < 0 C is left of AB (you can just check the numerator)
// If s>0 C is right of AB
// If s=0 C is on AB
int xdiff = xb - xa;
int ydiff = yb - ya;
long l2 = xdiff*xdiff + ydiff*ydiff;
if (l2 == 0) return Geom.length(xa, ya, xc, yc);
double rnum = (ya-yc) * (ya-yb) - (xa-xc) * (xb-xa);
double r = rnum / l2;
if (r < 0.0 || r > 1.0) return Double.MAX_VALUE;
double xi = xa + r * xdiff;
double yi = ya + r * ydiff;
double xd = xc - xi;
double yd = yc - yi;
return Math.sqrt(xd * xd + yd * yd);
/*
for directional version, instead use
double snum = (ya-yc) * (xb-xa) - (xa-xc) * (yb-ya);
double s = snum / l2;
double l = Math.sqrt((double)l2);
return = s * l;
*/
}
/**
* replacement for builtin Polygon.getBounds that doesn't always update?
*/
public static Rectangle bounds(Polygon p) {
int minx = Integer.MAX_VALUE;
int miny = Integer.MAX_VALUE;
int maxx = Integer.MIN_VALUE;
int maxy = Integer.MIN_VALUE;
int n = p.npoints;
for (int i = 0; i < n; i++) {
int x = p.xpoints[i];
int y = p.ypoints[i];
if (x > maxx) maxx = x;
if (x < minx) minx = x;
if (y > maxy) maxy = y;
if (y < miny) miny = y;
}
return new Rectangle(minx, miny, maxx-minx, maxy-miny);
}
public static Point center(Polygon p) {
long sx = 0;
long sy = 0;
int n = p.npoints;
for (int i = 0; i < n; i++) {
sx += p.xpoints[i];
sy += p.ypoints[i];
}
return new Point((int)(sx/n), (int)(sy/n));
}
public static Point chop(Polygon poly, Point p) {
Point ctr = center(poly);
int cx = -1;
int cy = -1;
long len = Long.MAX_VALUE;
// Try for points along edge
for (int i = 0; i < poly.npoints; ++i) {
int nxt = (i + 1) % poly.npoints;
Point chop = Geom.intersect(poly.xpoints[i],
poly.ypoints[i],
poly.xpoints[nxt],
poly.ypoints[nxt],
p.x,
p.y,
ctr.x,
ctr.y);
if (chop != null) {
long cl = Geom.length2(chop.x, chop.y, p.x, p.y);
if (cl < len) {
len = cl;
cx = chop.x;
cy = chop.y;
}
}
}
// if none found, pick closest vertex
//if (len == Long.MAX_VALUE) {
{ // try anyway
for (int i = 0; i < poly.npoints; ++i) {
long l = Geom.length2(poly.xpoints[i], poly.ypoints[i], p.x, p.y);
if (l < len) {
len = l;
cx = poly.xpoints[i];
cy = poly.ypoints[i];
}
}
}
return new Point(cx, cy);
}
}